#!/usr/bin/env python
# encoding: utf-8
# @author: Lin Han
# @contact: voldemort22@126.com
# @file: crud.py
# @time: 2021/3/19 18:38
# @desc:
import json
from datetime import datetime
from typing import Optional, Dict, List

import redis

from application.message_queue.rabbit_send_receive import RabbitMQClient
from cache.redis_pool import get_redis
from utils.extract_infomation import extract_standard_part_json
from utils.md5_tools import getFileMd5
from utils.mongo_tools import total_number_generator, retrieve_all_mongo_values
from utils.oss.oss_tools import upload_file_to_oss
from views.configs import *
from views.tag import *


def send_to_message_queue(
        model: str,
        queue: str = DEFAULT_QUEUE,
        mq_url=MQ_IP,
        mq_user=MQ_ACCOUNT,
        mq_password=MQ_PASSWORD,
        mq_port=MQ_PORT,
):
    """
    上传字典到消息队列，让耿晨洋那边转化类型，保存转化后的模型到OSS，记录模型的MD5值等一干信息到数据库里。

    :param mq_port:
    :param mq_password:
    :param mq_user:
    :param mq_url:
    :param queue:
    :param model:
    """
    mq_client = RabbitMQClient(username=mq_user, password=mq_password, host=mq_url, port=mq_port)
    mq_client.publish(
        message=json.dumps(model, ensure_ascii=False),
        exchange='',
        routing_key=queue,
        queue=queue,
    )
    try:
        print(f"{model} transmit to {mq_url}:{mq_port}消息队列服务器")
    except:
        pass


# def send_to_message_queue_pika(
#         model: Dict,
#         queue: str,
#         connection=connection,
#         # channel=channel,
# ):
#     channel = connection.channel()
#     channel.queue_declare(exchange='', queue=queue, durable=True)
#
#     channel.basic_publish(
#         exchange='',
#         routing_key='hello',
#         body=json.dumps(model, ensure_ascii=False),
#     )
#     print(model)
#     # connection.close()


def generate_data_including_workpiece(model: Dict, workpiece_file_path: str, oss_file_dirname="DODesign_Dev/pythonfiles", ):
    """
    给下面的历史夹具要添加工件的相关信息

    :param oss_file_dirname:
    :param model:
    :param workpiece_file_path:
    :return:
    """
    model["file"]["workpieceCatpartMd5"] = getFileMd5(workpiece_file_path)
    model["file"][WORKPIECE_CATPART_OSS_ADDRESS] = f"{oss_file_dirname}/{workpiece_file_path}"
    return model


def upload_to_aliyun_clamp_model(
        filename: str,
        workpiece_file_path: Optional[str] = None,
        desc: str = "",
        version: str = "1.0",
        isFeature: bool = False,
        isFlag: bool = False,
        oss_file_dirname="DODesign_Dev/pythonfiles",
) -> Dict:
    """

    :param type: 是6类模型中的哪一类
    :param workpiece_file_path: 工件在OSS上的地址
    :param oss_file_dirname:
    :param filename:
    :param isFlag:
    :param isFeature:
    :param desc:
    :param version:
    """
    model = {
        "number": str(NUMBER_HISTORY_CLAMP_START + total_number_generator(
            database_name=MONGO_DATABASE_NAME,
            collection_name="history_clamp",
        )),
        "time": datetime.now().strftime("%Y-%m-%d %H:%M"),
        # 存到数据库的name字段只应当包含最后一层的名字
        "name": filename.split("/")[-1],
        "desc": desc,
        "type": "1",
        "version": version,
        "isFeature": isFeature,
        "isFlag": isFlag,
        "tag": clampTagInit(),
        "file": {
            CATPART_OSS_ADDRESS: f"{oss_file_dirname}/{filename}",
            "catpartMd5": getFileMd5(filename),
            "transferFormatMessage": "not transfered yet",
        },
    }

    # 我觉得没有必要自动生成不存在的数据模型地址了，等耿晨阳上传后再添加进去吧
    print(upload_file_to_oss(local_file_name=filename, oss_file_dirname=oss_file_dirname))

    # 对历史夹具，由于还有绑定工件，如果工件的OSS路径不为空的话，把这个字典送到下面的函数中再添加一下工件各种模型的地址
    if workpiece_file_path is not None:
        print(upload_file_to_oss(local_file_name=workpiece_file_path, oss_file_dirname=oss_file_dirname))
        model = generate_data_including_workpiece(model, workpiece_file_path)

    send_to_message_queue(model)
    return model


def upload_to_aliyun_part_model(
        filename: str,
        desc: str = "",
        version: str = "1.0",
        isFeature: bool = False,
        isFlag: bool = False,
        isAbstract: bool = False,
        oss_file_dirname="DODesign_Dev/pythonfiles",
) -> Dict:
    """

    :param isAbstract:
    :param oss_file_dirname:
    :param filename:
    :param isFlag:
    :param isFeature:
    :param desc:
    :param version:
    """
    model = {
        "number": str(NUMBER_HISTORY_PART_START + total_number_generator(
            database_name=MONGO_DATABASE_NAME,
            collection_name=HISTORY_PART_COLLECTION,
        )),
        "time": datetime.now().strftime("%Y-%m-%d %H:%M"),
        "name": filename.split("/")[-1],
        "type": "2",
        "desc": desc,
        "version": version,
        "isFeature": isFeature,
        "isFlag": isFlag,
        "isAbstract": isAbstract,
        "tag": partTagInit(),
        "file": {
            CATPART_OSS_ADDRESS: f"{oss_file_dirname}/{filename}",
            "catpartMd5": getFileMd5(filename),
            "transferFormatMessage": "not transfered yet",
        }
    }

    print(upload_file_to_oss(local_file_name=filename, oss_file_dirname=oss_file_dirname))
    send_to_message_queue(model)
    return model


def upload_to_aliyun_standard_clamp_model(
        filename: str,
        jsonData: dict,
        desc: str = "",
        version: str = "1.0",
        isFeature: bool = False,
        isFlag: bool = False,
        oss_file_dirname="DODesign_Dev/pythonfiles",
) -> Dict:
    """

    :param jsonData:
    :param oss_file_dirname:
    :param filename:
    :param isFlag:
    :param isFeature:
    :param desc:
    :param version:
    """
    model = {
        "number": str(NUMBER_STANDARD_CLAMP_START + total_number_generator(
            database_name=MONGO_DATABASE_NAME,
            collection_name=STANDARD_CLAMP_COLLECTION,
        )),
        "time": datetime.now().strftime("%Y-%m-%d %H:%M"),
        "name": filename.split("/")[-1],
        "desc": desc,
        "type": "3",
        "version": version,
        "isFeature": isFeature,
        "isFlag": isFlag,

        "file": {
            JSON_OSS_ADDRESS: f"{oss_file_dirname}/{filename}",
            "jsonMd5": getFileMd5(filename),
            "jsonData": jsonData,
            # 对03类型的模型，这里需要帮耿晨洋把json中包含的04模型的数据都取出来
            "standardPart": extract_standard_part_json(filename),
            "transferFormatMessage": "not transfered yet",
        }
    }

    # 注意：现阶段还不能把json格式的标准夹具转化为.gltf格式的，只能转化为.stl的，所以其他格式的假路径暂时也不用生成了

    print(upload_file_to_oss(local_file_name=filename, oss_file_dirname=oss_file_dirname))
    return model


def upload_to_aliyun_standard_part_model(
        filename: str,
        jsonData: dict,
        category: str,
        desc: str = "",
        version: str = "1.0",
        isFeature: bool = False,
        isFlag: bool = False,
        oss_file_dirname="DODesign_Dev/pythonfiles",
) -> Dict:
    """

    :param jsonData:
    :param category:
    :param oss_file_dirname:
    :param filename:
    :param isFlag:
    :param isFeature:
    :param desc:
    :param version:
    """
    model = {
        "number": str(NUMBER_STANDARD_PART_START + total_number_generator(
            database_name=MONGO_DATABASE_NAME,
            collection_name=STANDARD_PART_COLLECTION,
        )),
        "time": datetime.now().strftime("%Y-%m-%d %H:%M"),
        "name": filename.split("/")[-1],
        "category": category,
        "desc": desc,
        "type": "4",
        "version": version,
        "isFeature": isFeature,
        "isFlag": isFlag,
        "file": {
            JSON_OSS_ADDRESS: f"{oss_file_dirname}/{filename}",
            "jsonMd5": getFileMd5(filename),
            "jsonData": jsonData,
            "transferFormatMessage": "not transfered yet",
        }
    }

    print(upload_file_to_oss(local_file_name=filename, oss_file_dirname=oss_file_dirname))
    send_to_message_queue(model)
    return model


def upload_to_aliyun_parameter_model(
        filename: str,
        desc: str = "",
        version: str = "1.0",
        isFeature: bool = False,
        isFlag: bool = False,
        oss_file_dirname="DODesign_Dev/pythonfiles",
) -> Dict:
    """
    处理上传catia文件到阿里OSS的视图函数。先计算catia文件的MD5值，拿到名字（name），生成唯一id（number），

    :param type:
    :param oss_file_dirname:
    :param filename:
    :param isFlag:
    :param isFeature:
    :param desc:
    :param version:
    """
    model = {
        "number": str(NUMBER_PARAMETER_MODEL_START + total_number_generator(
            database_name=MONGO_DATABASE_NAME,
            collection_name=PARAMETER_MODEL_COLLECTION,
        )),
        "time": datetime.now().strftime("%Y-%m-%d %H:%M"),
        "name": filename.split("/")[-1],
        "desc": desc,
        "type": "5",
        "version": version,
        "isFeature": isFeature,
        "isFlag": isFlag,
        "tag": parameterTagInit(),
        "file": {
            "parameter": {},
            CATPART_OSS_ADDRESS: f"{oss_file_dirname}/{filename}",
            "catpartMd5": getFileMd5(filename),
            "transferFormatMessage": "not transfered yet",
        }
    }

    print(upload_file_to_oss(local_file_name=filename, oss_file_dirname=oss_file_dirname))
    send_to_message_queue(model)
    return model


def create_scene(
        name: str,
        desc: str,
        rpsNumber: int,
) -> Dict:
    """
    根据用户输入的信息，创建一条场景数据。需要要注意的是，创建阶段只输入名字、描述和焊点数，绑定的夹具另外做。
    """
    model = {
        "number": str(NUMBER_SCENE_START + total_number_generator(
            database_name=MONGO_DATABASE_NAME,
            collection_name=SCENE_COLLECTION,
        )),
        "time": datetime.now().strftime("%Y-%m-%d %H:%M"),
        "name": name,
        "desc": desc,
        "rpsNumber": rpsNumber,
        "type": "6",
        "historyClamp": [],
        "standardClamp": [],
    }

    return model


def keyword_search_history(
        queryModel,
        result: list,
) -> list:
    """
    列表查询多条历史夹具或历史零件。

    :param queryModel: 前端传过来的参数，原封不动地传过来。
    :param result: 事先从历史夹具的数据库中把所有数据都取出来，以供搜索使用。
    """
    filtered_result = []
    for i in result:
        i_time = datetime.strptime(i["time"], "%Y-%m-%d %H:%M")
        # 按关键字查询
        if queryModel.keyword is None or queryModel.keyword.lower() in i["number"].lower() or queryModel.keyword.lower() in i["name"].lower():
            if queryModel.isFlag is None or not queryModel.isFlag or queryModel.isFlag == i['isFlag']:
                if queryModel.isFeature is None or not queryModel.isFeature or queryModel.isFeature == i['isFeature']:
                    if queryModel.standard is None or queryModel.standard in i['tag']['standard']:
                        if queryModel.startTime is None or queryModel.endTime is None or queryModel.startTime < i_time < queryModel.endTime:
                            filtered_result.append(i)
    return filtered_result


def keyword_search_standard_clamp(
        queryModel,
        result: list,
) -> list:
    """
    列表查询多条标准夹具。

    :param queryModel:
    :param result:
    """
    filtered_result = []
    for i in result:
        # 按关键字查询
        if queryModel.keyword is None or queryModel.keyword.lower() in i["number"] or queryModel.keyword.lower() in i["name"].lower():
            if queryModel.version is None or queryModel.version == i['version']:
                if queryModel.startTime is None or queryModel.endTime is None or queryModel.startTime < datetime.strptime(i["time"], "%Y-%m-%d %H:%M") < queryModel.endTime:
                    filtered_result.append(i)
    # return filtered_result
    return sorted(filtered_result, key=lambda x: x["name"])


def keyword_search_standard_part(
        queryModel,
        result: list,
) -> list:
    """
    列表查询多条标准零件。

    :param queryModel:
    :param result:
    """
    filtered_result = []
    for i in result:
        # 按关键字查询
        if queryModel.keyword is None or queryModel.keyword.lower() in i["number"] or queryModel.keyword.lower() in i["name"].lower():
            if queryModel.version is None or queryModel.version == i['version']:
                if queryModel.startTime is None or queryModel.endTime is None or queryModel.startTime < datetime.strptime(i["time"], "%Y-%m-%d %H:%M") < queryModel.endTime:
                    if queryModel.partFunction is None or queryModel.partFunction == i["partFunction"]:
                        filtered_result.append(i)
    # return filtered_result
    return sorted(filtered_result, key=lambda x: x["name"])


def keyword_search_parameter(
        queryModel,
        result: list,
) -> list:
    """
    列表查询多条参数化模型。

    :param queryModel: 前端传过来的参数，原封不动地传过来。
    :param result: 事先从历史夹具的数据库中把所有数据都取出来，以供搜索使用。
    """
    filtered_result = []
    for i in result:
        try:
            i_time = datetime.strptime(i["time"], "%Y-%m-%d %H:%M")
            # 按关键字查询
            if queryModel.keyword is None or queryModel.keyword.lower() in i["number"].lower() or queryModel.keyword.lower() in i["name"].lower():
                if queryModel.isFlag is None or not queryModel.isFlag or queryModel.isFlag == i['isFlag']:
                    if queryModel.isFeature is None or not queryModel.isFeature or queryModel.isFeature == i['isFeature']:
                        if queryModel.standard is None or queryModel.standard == i['tag']['standard']:
                            if queryModel.partType is None or queryModel.partType == i['tag']['partType']:
                                if queryModel.category is None or queryModel.category == i['tag']['category']:
                                    if queryModel.startTime is None or queryModel.endTime is None or queryModel.startTime < i_time < queryModel.endTime:
                                        filtered_result.append(i)
        except:
            continue
    return filtered_result


def keyword_search_scene(
        queryModel,
        result: list,
) -> list:
    """
    列表查询多条参数化模型。

    :param queryModel: 前端传过来的参数，原封不动地传过来。
    :param result: 事先从历史夹具的数据库中把所有数据都取出来，以供搜索使用。
    """
    filtered_result = []
    for i in result:
        try:
            i_time = datetime.strptime(i["time"], "%Y-%m-%d %H:%M")
            # 按关键字查询
            if queryModel.keyword is None or queryModel.keyword in i["number"] or queryModel.keyword.lower() in i["name"].lower() or queryModel.keyword.lower() in i["desc"].lower():
                if queryModel.rpsNumber is None or queryModel.rpsNumber == i['rpsNumber']:
                    if queryModel.startTime is None or queryModel.endTime is None or queryModel.startTime < i_time < queryModel.endTime:
                        filtered_result.append(i)
        except:
            continue
    # return filtered_result
    return sorted(filtered_result, key=lambda x: int(x["name"]))


def separate_page(result: list, page: int, pagesize: int):
    """
    分页功能。

    :param result: 待分页的列表
    :param page: 页号
    :param pagesize: 每页多少条数据
    """
    result = [result[i:i + pagesize] for i in range(0, len(result), pagesize)][page - 1]
    return result


def calculate_page(number: str, total_data: List[str], pagesize: int = 10):
    """
    计算该数据在其类型中应在第几页和该项的第几项，注意返回给前端的页码是从1开始而不是从0开始的。

    :param pagesize: 每页展示数量，默认为10
    :param number: 数据的id
    :param total_data: 排序过后的id的列表，不含其他元素了
    """
    position = total_data.index(number)
    return position // pagesize + 1, position % pagesize + 1


def is_part_in_database(
        part_list: List,
        database_name: str,
        collection_name: str,
) -> bool:
    """
    判断part_list列表里的所有信息是否都在collection_name表里。

    :param database_name:
    :param part_list:
    :param collection_name:
    """
    # 取出数据库的标准零件表中所有元素
    standard_part_list = retrieve_all_mongo_values({}, database_name=database_name, collection_name=collection_name)
    # 上一列表替换成仅有标准零件名字的列表
    standard_part_name_list = [i["category"] for i in standard_part_list]
    part_list = [i["category"] for i in part_list]
    if set(part_list) <= set(standard_part_name_list):
        return True
    else:
        return False


def retrieve_redis_values(number_list: list):
    """
    返回根据number的列表从redis中取得的数据（已经反序列化了）

    :return:
    """
    redis = get_redis()
    scene = redis.mget(*number_list)
    return [json.loads(i) for i in scene if i is not None]


def get_history_clamp_in_all_scene():
    # 获取所有场景中包含的所有历史夹具的id
    result = []
    scenes = retrieve_all_mongo_values({}, collection_name=SCENE_COLLECTION)
    for i in scenes:
        if "historyClamp" in i.keys() and type(i["historyClamp"]) is list:
            result.extend(i["historyClamp"])
    return list(set(result))


def get_standard_clamp_in_all_scene():
    # 获取所有场景中包含的所有标准夹具的id
    result = []
    # scenes = retrieve_all_scene_redis_values()
    scenes = retrieve_all_mongo_values({}, collection_name=SCENE_COLLECTION)
    for i in scenes:
        if "standardClamp" in i.keys() and type(i["standardClamp"]) is list:
            result.extend(i["standardClamp"])
    return list(set(result))


def sort_redis_result(model: dict, number: str, data_type: str, redis_connection: redis.Redis):
    keys = redis_connection.get(data_type)
    keys = json.loads(keys)
    total_data = redis_connection.mget(*keys)
    total_data = [json.loads(i) for i in total_data]
    total_data = sorted(total_data, key=lambda x: x["name"])
    total_data = [i["number"] for i in total_data]
    model["data"]["page"], model["data"]["position"] = calculate_page(number, total_data=total_data)


if __name__ == '__main__':
    send_to_message_queue("2021020000000236")